package com.zdft.promotion.logging.service.impl;

import com.zdft.promotion.common.model.QueryResultModel;
import com.zdft.promotion.common.service.BusinessException;
import com.zdft.promotion.common.service.api.ICacheService;
import com.zdft.promotion.common.util.ExceptionUtil;
import com.zdft.promotion.logging.OperationLogFilterModel;
import com.zdft.promotion.logging.bean.OperationLogBean;
import com.zdft.promotion.logging.dao.OperationLogDAO;
import com.zdft.promotion.logging.service.api.APIOperationLog;
import com.zdft.promotion.logging.service.api.IOperationLogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedTransferQueue;

/**
 * Created by Administrator on 2019/2/21.
 */
@Service
public class OperationLogServiceImpl implements IOperationLogService
{
    private static final Logger log = LoggerFactory.getLogger(OperationLogServiceImpl.class);

    private static final String PREFIX_OPER_LOG = "operation_log_";

    @Resource private OperationLogDAO operationLogDAO;

    @Resource private ICacheService cacheService;

    private final BlockingQueue<OperationLogBean> logQueue;
    private final Executor                        executor;

    private volatile boolean running = false;

    public OperationLogServiceImpl()
    {
        this.logQueue = new LinkedTransferQueue<>();
        this.executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    }

    @Override
    public void asyncAddOperationLog(Method method, String paramValue, String returnValue, String operator
            , Date operateTime) throws BusinessException
    {
        log.debug("Async add operation log method:{},paramValue:{},returnValue:{},operator:{},operateTime:{}...",
                  method, paramValue, returnValue, operator, operateTime);
        executor.execute(new AsyncAddOperationLogImpl(method, paramValue, returnValue, operator, operateTime)
        {
            @Override
            protected void insert(OperationLogBean bean)
            {
                logQueue.offer(bean);
            }
        });
    }

    @Transactional
    @Override
    public QueryResultModel<OperationLogBean> queryOperationLog(OperationLogFilterModel filter)
    {
        long count = operationLogDAO.filterCount(filter);
        if (count <= 0)
        {
            return new QueryResultModel<>(0, null);
        }
        List<OperationLogBean> list = operationLogDAO.filterList(filter);
        return new QueryResultModel<>(count, list);
    }

    @Override
    public OperationLogBean loadOperationLog(Long id) throws BusinessException
    {
        if (null == id)
        {
            throw new IllegalArgumentException("Must provide id!");
        }
        String key = PREFIX_OPER_LOG + id;
        OperationLogBean bean = cacheService.get(key, operationLogDAO::getByPrimaryKey, id, 1800);
        if (null == bean)
        {
            throw new BusinessException("00", id);
        }
        return bean;
    }

    @Override
    public void deleteOperationLog(Long id)
    {
        if (null == id)
        {
            throw new IllegalArgumentException("Must provide id!");
        }
        if (operationLogDAO.delete(id) <= 0)
        {
            throw new BusinessException("01");
        }
        cacheService.delete(PREFIX_OPER_LOG + id);
    }

    @Scheduled(cron = "0/1 * * * * ?")
    public void batchInsertLogs()
    {
        System.out.println(System.currentTimeMillis());
        if (running)
        {
            return;
        }

        running = true;

        List<OperationLogBean> beanList = new ArrayList<>();
        try
        {
            OperationLogBean bean = logQueue.take();
            while (null != bean)
            {
                beanList.add(bean);
                bean = logQueue.poll();
            }
            int count = beanList.size();
            operationLogDAO.batchInsert(beanList);
            log.info("Insert operation log successfully, count:{}.", count);
        }
        catch (Throwable throwable)
        {
            log.error(ExceptionUtil.toString(throwable));
        }
        finally
        {
            running = false;
        }
    }
}

abstract class AsyncAddOperationLogImpl implements Runnable
{
    private static final Logger log = LoggerFactory.getLogger(AsyncAddOperationLogImpl.class);

    private final Method method;
    private final String paramValue;
    private final String returnValue;
    private final String operator;
    private final Date   operateTime;

    AsyncAddOperationLogImpl(Method method, String paramValue, String returnValue, String operator, Date operateTime)
    {
        this.method = method;
        this.paramValue = paramValue;
        this.returnValue = returnValue;
        this.operator = operator;
        this.operateTime = operateTime;
    }

    @Override
    public void run()
    {
        try
        {
            APIOperationLog ann = method.getDeclaredAnnotation(APIOperationLog.class);

            OperationLogBean bean = new OperationLogBean();
            bean.setModule("".equals(ann.module()) ? method.getDeclaringClass().getName() : ann.module());
            bean.setFunction("".equals(ann.function()) ? method.getName() : ann.function());
            bean.setDesc(ann.desc());
            bean.setParams(paramValue);
            bean.setReturns(returnValue);
            bean.setOperateTime(operateTime);
            bean.setCreateUser(operator);
            bean.setCreateTime(new Date());
            log.debug("Add operation log to queue:{}...", bean);
            insert(bean);
        }
        catch (Throwable throwable)
        {
            log.error(ExceptionUtil.toString(throwable));
        }
    }

    protected abstract void insert(OperationLogBean bean);
}